Long only 1/n portfolio#
import pandas as pd
pd.options.plotting.backend = "plotly"
import yfinance as yf
from cvx.simulator.builder import builder
from cvx.simulator.grid import resample_index
# Get rid of findfont: Font family 'Arial' not found.
# when running a remote notebook on Jupyter Server on Ubuntu Linux server
import logging
logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR)
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 import pandas as pd
2 pd.options.plotting.backend = "plotly"
4 import yfinance as yf
ModuleNotFoundError: No module named 'pandas'
data = yf.download(tickers = "SPY AAPL GOOG MSFT", # list of tickers
period = "10y", # time period
interval = "1d", # trading interval
prepost = False, # download pre/post market hours data?
repair = True) # repair obvious price errors e.g. 100x?
[*********************100%***********************] 4 of 4 completed
prices = data["Adj Close"]
capital = 1e6
b = builder(prices=prices, initial_cash=capital)
for time, state in b:
# each day we invest a quarter of the capital in the assets
b[time[-1]] = 0.25 * state.nav / state.prices
portfolio = b.build()
portfolio.profit.cumsum().plot()
portfolio.nav.plot()
## Rebalancing
Usually we would not execute on a daily basis but rather rebalance every week, month or quarter.
There are two approaches to deal with this problem in cvxsimulator.
* Resample the existing daily portfolio (helpful to see effect of your hesitated trading)
* Trade only on days that are within a predefined grid (most flexible if you have a rather irregular grid)
### Resample an existing portfolio
portfolio_resampled = portfolio.resample(rule="M")
frame = pd.DataFrame({"original": portfolio.nav, "monthly": portfolio_resampled.nav})
frame
| original | monthly | |
|---|---|---|
| Date | ||
| 2013-05-30 | 1.000000e+06 | 1.000000e+06 |
| 2013-05-31 | 9.945921e+05 | 9.945921e+05 |
| 2013-06-03 | 1.000400e+06 | 1.000391e+06 |
| 2013-06-04 | 9.917402e+05 | 9.917309e+05 |
| 2013-06-05 | 9.846364e+05 | 9.846127e+05 |
| ... | ... | ... |
| 2023-05-23 | 7.505451e+06 | 7.493060e+06 |
| 2023-05-24 | 7.461416e+06 | 7.447306e+06 |
| 2023-05-25 | 7.603349e+06 | 7.590982e+06 |
| 2023-05-26 | 7.711937e+06 | 7.698989e+06 |
| 2023-05-30 | 7.735463e+06 | 7.721169e+06 |
2518 rows × 2 columns
print(portfolio_resampled.stocks)
AAPL GOOG MSFT SPY
Date
2013-05-30 17854.390520 11527.266005 8564.268967 1814.033248
2013-05-31 17854.390520 11527.266005 8564.268967 1814.033248
2013-06-03 17895.614489 11573.477982 8432.882983 1831.100869
2013-06-04 17895.614489 11573.477982 8432.882983 1831.100869
2013-06-05 17895.614489 11573.477982 8432.882983 1831.100869
... ... ... ... ...
2023-05-23 10590.586692 16651.905094 5882.633509 4316.566726
2023-05-24 10590.586692 16651.905094 5882.633509 4316.566726
2023-05-25 10590.586692 16651.905094 5882.633509 4316.566726
2023-05-26 10590.586692 16651.905094 5882.633509 4316.566726
2023-05-30 10590.586692 16651.905094 5882.633509 4316.566726
[2518 rows x 4 columns]
# almost hard to see that difference between the original and resampled portfolio
frame.plot()
# number of shares traded
portfolio_resampled.trades_stocks.iloc[1:].plot()
Trade only days in predefined grid#
b = builder(prices=prices, initial_cash=capital)
# define a grid
grid = resample_index(prices.index, rule="M")
for time, state in b:
# each day we invest a quarter of the capital in the assets
if time[-1] in grid:
b[time[-1]] = 0.25 * state.nav / state.prices
else:
# forward fill an existing position
b[time[-1]] = b[time[-2]]
portfolio = b.build()
portfolio.nav.plot()
# Trading only once a month can lead to days where 150k had to be reallocated
portfolio.turnover.iloc[1:].plot()
Why not resampling the prices?#
I don’t believe in bringing the prices to a monthly grid. This would render it hard to construct signals given the sparse grid. We stay on a daily grid and trade once a month.